using System;
using System.Collections.Generic;
using System.Text;

namespace Xivo.Shared {
  public class XM : RadioBase, IXivoPlugin {
    public delegate void ResponseReceivedEvent(PCREventArgs e);
    public event ResponseReceivedEvent onResponseReceived;

    /// <summary>
    /// Fired when the radio is turned on or off
    /// </summary>
    public override event Delegates.IntEvent onPowerChange;
    /// <summary>
    /// Fired when the current channel is changed
    /// </summary>
    public override event Delegates.IntEvent onChannelChange;
    /// <summary>
    /// Fired when the radio reports mute turned on or off.
    /// </summary>
    public override event Delegates.IntEvent onMuteChange;
    /// <summary>
    /// Fired when the current channel's information is updated.
    /// </summary>
    public override event Delegates.InfoEvent onChannelInfoChange;
    /// <summary>
    /// Fired when ANY channel's information is updated.
    /// </summary>
    public override event Delegates.InfoEvent onChannelInfoReceived;
    /// <summary>
    /// Returns radio ID or serial number.
    /// </summary>
    public override event Delegates.StringEvent onRadioIDReceived;
    /// <summary>
    /// antenna: 1-Sat, 2-Terr, 0-antenna state (3-connected, 0-disconnected)
    /// data: 0-bad, 3-good
    /// </summary>
    public override event Delegates.SignalEvent onSignalDataReceived;

    SerialIO IO = new SerialIO();
    Queue<ChannelInfo> ChannelInfoRequestList = new Queue<ChannelInfo>();

    int _CurrentChannel;
    int _PowerState = 0;
    public string _RadioID;
    public override  string RadioID {
      get { return _RadioID; }
    }

    public override string RadioType {
      get { return "XMPCR"; }
    }

    bool gettingAllChannels;
    public override  bool Busy {
      get {
        if (ChannelInfoRequestList.Count > 0 || gettingAllChannels)
          return true;
        else
          return false;
      }
    }

    public XM() {
    }

    public override string PortName {
      get { return IO.PortName; }
      set { IO.PortName = value; }
    }

    public override int PowerState {
      get { return _PowerState; }
      set {
        DesiredPowerState = value;
        if (value == 0)
          PowerOff();
        else
          PowerOn();
      }
    }

    void IXivoPlugin.Init() {
      IO.onResponseReceived += new ResponseReceivedEvent(IO_onResponseReceived);
      channels.LoadList(Common.Radio.channels.AllChannels, "channels.txt");
      channels.LoadList(Common.Radio.channels.Favorites, "favorites.txt");
    }

    void IO_onResponseReceived(PCREventArgs e) {
      bool channelEvent = false;
      switch (e.responseNumber) {
        case ResponseNumberEnum.powerUp:
          _PowerState = 1;
          if (onPowerChange != null)
            onPowerChange(_PowerState);
          getRadioID();
          ChangeChannel(_CurrentChannel);
          break;
        case ResponseNumberEnum.powerDown:
          _PowerState = 0;
          if (onPowerChange != null)
            onPowerChange(_PowerState);
          break;
        case ResponseNumberEnum.getChannelInfo:
          receivedChannelInfo(e);
          channelEvent = true;
          break;
        case ResponseNumberEnum.getExtendedChannelInfo:
          receivedExtendedChannelInfo(e);
          channelEvent = true;
          break;
        case ResponseNumberEnum.changeChannel:
          receivedChannelChange(e);
          channelEvent = true;
          break;
        case ResponseNumberEnum.channelArtistTitleChange:
          receivedArtistTitleChange(e);
          channelEvent = true;
          break;
        case ResponseNumberEnum.radioOff:
          _PowerState = 0;
          if (onPowerChange != null)
            onPowerChange(_PowerState);
          if (DesiredPowerState == 1)
            PowerState = DesiredPowerState;
          break;
        case ResponseNumberEnum.monitorChannelData:
          break;
        case ResponseNumberEnum.getRadioID:
          receivedRadioID(e);
          break;
        case ResponseNumberEnum.getSignalData:
          receivedSignalData(e);
          break;
        case ResponseNumberEnum.receivedSignalData:
          receivedAutoSignalData(e);
          break;
        case ResponseNumberEnum.channelExtArtistChange:
          receivedExtArtist(e);
          channelEvent = true;
          break;
        case ResponseNumberEnum.channelExtTitleChange:
          receivedExtTitle(e);
          channelEvent = true;
          break;
        case ResponseNumberEnum.monitorSignalData:
          break;
        case ResponseNumberEnum.channelSongTime:
          break;
        case ResponseNumberEnum.setMute:
          _MuteState = e.RawData[3];
          if (onMuteChange != null)
            onMuteChange(_MuteState);
          break;
        default:
          displayBuffer("Response not handled", e.RawData, e.length);
          break;
      }

      if (channelEvent && e.channelInfo != null) {
        channels.UpdateChannelInfo(e.channelInfo);
        if (onChannelInfoReceived != null)
          onChannelInfoReceived(e.channelInfo);
        if (_CurrentChannel == e.channelInfo.channel && onChannelInfoChange != null)
          onChannelInfoChange(e.channelInfo);
      }

      if (onResponseReceived != null)
        onResponseReceived(e);
    }

    private void receivedChannelChange(PCREventArgs e) {
      //displayBuffer("Channel Change", e.RawData);
      if (e.RawData[2] != 0) // invalid channel
        channels.AllChannels.Remove(e.RawData[4]);
      else {
        _CurrentChannel = e.RawData[4];
        e.channelInfo = channels.Channel(_CurrentChannel);
        e.channelInfo.channel = _CurrentChannel;
        getChannelInfo(e.channelInfo.channel);
      }
      MonitorChannelData(CurrentChannel, true);

      if (onChannelChange != null)
        onChannelChange(_CurrentChannel);
    }

    private void receivedExtTitle(PCREventArgs e) {
      if (e.RawData[2] != 0) {
        ChannelInfo i = channels.Channel(e.RawData[1]);
        e.channelInfo = i;
        i.longTitle = e.GetString(3, 32);
        i.updateType = ChannelInfo.UpdateType.longName;
      }
    }

    private void receivedExtArtist(PCREventArgs e) {
      if (e.RawData[2] != 0) {
        ChannelInfo i = channels.Channel(e.RawData[1]);
        e.channelInfo = i;
        i.longArtist = e.GetString(3, 32);
        i.updateType = ChannelInfo.UpdateType.longName;
      }
    }

    private void receivedSignalData(PCREventArgs e) {
      //displayBuffer("Signal", e.RawData);
      e.Signal = new SignalData();
      e.Signal.Sattelite = e.RawData[3];
      e.Signal.antennaState = e.RawData[4];
      e.Signal.Terrestrial = e.RawData[11];

      invokeSignalDataReceived(e.Signal);

      MonitorSignalData(true);
    }

    private void receivedAutoSignalData(PCREventArgs e) {
      //displayBuffer("Auto Signal", e.RawData);
      e.responseNumber = ResponseNumberEnum.getSignalData;
      e.Signal = new SignalData();
      e.Signal.Sattelite = e.RawData[1];
      e.Signal.antennaState = e.RawData[2];
      e.Signal.Terrestrial = e.RawData[9];

      invokeSignalDataReceived(e.Signal);
    }

    private void invokeSignalDataReceived(SignalData Signal) {
      if (onSignalDataReceived != null) {
        onSignalDataReceived(new int[] { Signal.antennaState, Signal.Sattelite, Signal.Terrestrial });
      }
    }

    void IXivoPlugin.Start() {
      IO.Open();
      DetectRadio();
    }

    void IXivoPlugin.ShutDown() {
      IO.onResponseReceived -= IO_onResponseReceived;
      MonitorChannelData(CurrentChannel, false );
      MonitorSignalData(false);
      Common.Radio.channels.Save(Common.Radio.channels.AllChannels, "channels.txt");
      Common.Radio.channels.Save(Common.Radio.channels.Favorites, "favorites.txt");
      IO.Close();
    }

    public override int CurrentChannel {
      get { return _CurrentChannel; }
      set {
        _CurrentChannel = value;
        if (PowerState == 1)
          ChangeChannel(_CurrentChannel);
      }
    }

    public void DetectRadio() {
      getRadioID();
    }

    private void PowerOff() {
      byte[] data = new byte[] {
        0x01, 0x00 };
      IO.SendCommand(data);
    }

    private void PowerOn() {
      // fire up an XM Direct receiver. Not needed for the PCR, but it doesn't hurt.
      byte[] cmd = new byte[] { 0x74, 0x00, 0x01 };
      IO.SendCommand(cmd);
      System.Threading.Thread.Sleep(500);
      cmd = new byte[] { 0x74, 0x02, 0x01, 0x01 };
      IO.SendCommand(cmd);
      System.Threading.Thread.Sleep(500);
      cmd = new byte[] { 0x74, 0x0b, 0x00 };
      IO.SendCommand(cmd);
      System.Threading.Thread.Sleep(500);

      byte[] data = new byte[] {
        0x00, 0x10, 0x10, 0x10, 0x01 };
      IO.SendCommand(data);
    }

    public void ChangeChannel(int channel) {
      MonitorChannelData(_CurrentChannel, false);
      IO.SendCommand(new byte[] { 
        (byte) CommandNumberEnum.changeChannel, 0x02, (byte)channel, 0x00, 0x00, 0x01 });
    }


    public void getRadioID() {
      byte[] data = new byte[] {
        0x31 };
      IO.SendCommand(data);
    }

    /// <summary>
    /// Gives us the ID of the attached XM receiver.
    /// this can also be used to test powered-up status, so the first thing we do
    /// when initializing the radio is request the radio ID. If an ID is received,
    /// the radio is turned on, so we set the initial channel and inform the app
    /// that the radio is on.
    /// </summary>
    /// <param name="e"></param>
    public void receivedRadioID(PCREventArgs e) {
      _RadioID = e.GetString(4, 8);
      if (PowerState == 0) {
        _PowerState = 1;
        if (onPowerChange != null)
          onPowerChange(_PowerState);
        ChangeChannel(_CurrentChannel);
      }
      requestSignalData();
      if (onRadioIDReceived != null)
        onRadioIDReceived(RadioID);
    }

    public void MonitorSignalData(bool enabled) {
      byte b = (byte)(enabled ? 1 : 0);
      IO.SendCommand(new byte[] { (byte)CommandNumberEnum.monitorSignalData, b });
    }

    public void setMute(byte mute) {
      byte[] data = new byte[] {
        0x13, mute };
      IO.SendCommand(data);
    }

    public void MonitorChannelData(int channel, bool enabled) {
      byte b = (byte)(enabled ? 1 : 0);
      IO.SendCommand(new byte[] { (byte) CommandNumberEnum.monitorChannelData ,
        (byte)channel, 0, 0, b, b });
    }

    public override  void EnqueueChannelInfoRequest(ChannelInfo info) {
      ChannelInfoRequestList.Enqueue(info);
    }

    public override void ServiceChannelInfoQueue() {
      if (!gettingAllChannels && ChannelInfoRequestList.Count > 0)
        getChannelInfo(ChannelInfoRequestList.Dequeue());
    }

    public void getChannelInfo(ChannelInfo info) {
      if (!gettingAllChannels)
        getChannelInfo(info.channel);
    }

    public override  void requestAllChannelInfo() {
      gettingAllChannels = true;
      getChannelInfo(0, true);
    }

    public void getChannelInfo(int channel) {
      if (!gettingAllChannels)
        getChannelInfo(channel, false);
    }

    private void getChannelInfo(int channel, bool gettingNext) {
      byte mode;
      if (gettingNext)
        mode = 9;
      else
        mode = 8;
      byte[] data = new byte[] {
        0x25, mode, (byte) channel, 0 };
      IO.SendCommand(data);
    }

    public void getExtendedChannelInfo(int channel) {
      IO.SendCommand(new byte[] { 0x22, (byte)channel });
    }

    public override void requestSignalData() {
      byte[] data = new byte[] {
        0x43 };
      IO.SendCommand(data);
    }

    static public void displayBuffer(string Desc, byte[] buffer, int len) {
      System.Diagnostics.Debug.WriteLine(Desc);
      int i = 0;
      while (i < len) {
        System.Diagnostics.Debug.Write(i.ToString("X2") + " : ");
        for (int j = i; j < i + 16; ++j) {
          if (j < buffer.Length)
            System.Diagnostics.Debug.Write(buffer[j].ToString("X2"));
          else
            System.Diagnostics.Debug.Write("  ");
          System.Diagnostics.Debug.Write(' ');
        }
        System.Diagnostics.Debug.Write(": ");
        for (int j = i; j < i + 16; ++j) {
          char c;
          if (j < buffer.Length)
            c = (char)buffer[j];
          else
            c = ' ';
          if (c < ' ' || c > '~')
            c = '.';
          System.Diagnostics.Debug.Write(c);
        }
        i += 16;
        System.Diagnostics.Debug.WriteLine(" :");
      }
      System.Diagnostics.Debug.WriteLine("|");
    }

    byte[] lastBuffer = new byte[256];
    void receivedChannelInfo(PCREventArgs e) {
      string Station, Genre, Artist, Title;
      int StationValid, GenreValid, ArtistTitleValid;
      int channelNumber;

      // extract data from the message
      channelNumber = e.RawData[3];
      StationValid = e.RawData[5];
      Station = e.GetString(6, 16);
      GenreValid = e.RawData[23];
      Genre = e.GetString(24, 16);
      ArtistTitleValid = e.RawData[40];
      Artist = e.GetString(41, 16);
      Title = e.GetString(57, 16);


      if (channelNumber > 0) {
        ChannelInfo i = channels.Channel(channelNumber);
        e.channelInfo = i;
        // check all the ways a messsage might be bad
        if (e.RawData[1] == 0   // channel not valid or blocked
          //|| (Station != "" && Station == lastStation)
           || e.RawData[4] == 0xff) // workaround for XM Direct bug
        {

          // debug
          displayBuffer("Last Channel Info", lastBuffer, 0x4f);
          displayBuffer("Channel Info", e.RawData, 0x4f);

          e.channelInfo = new ChannelInfo();
          e.channelInfo.valid = false;
          e.channelInfo.channel = channelNumber;
        }
        else {
          i.valid = true;
          i.updateType = ChannelInfo.UpdateType.shortName;
          if (StationValid != 0)
            i.station = Station;
          if (GenreValid != 0)
            i.genre = Genre;
          if (ArtistTitleValid != 0) {
            if (i.shortArtist != Artist) {
              i.shortArtist = Artist;
              i.longArtist = Artist;
            }
            if (i.shortTitle != Title) {
              i.shortTitle = Title;
              i.longTitle = Title;
            }
          }
          e.RawData.CopyTo(lastBuffer, 0);
        }
      }

      if (channelNumber > 0 && gettingAllChannels)
        getChannelInfo(channelNumber, true);
      else if (ChannelInfoRequestList.Count > 0) {
        gettingAllChannels = false;
        ServiceChannelInfoQueue();
      }
      else {
        //if (channelNumber == currentChannel)
        //  getExtendedChannelInfo(currentChannel);
        gettingAllChannels = false;
      }
    }

    void receivedExtendedChannelInfo(PCREventArgs e) {
      if (e.RawData[1] != 0) {
        ChannelInfo i = channels.Channel(e.RawData[3]);
        e.channelInfo = i;
        if (e.RawData[1] != 0x0) {
          i.valid = true;
          i.updateType = ChannelInfo.UpdateType.longName;
          i = channels.Channel(e.RawData[3]);
          if (e.RawData[4] != 0x00)
            i.longArtist = e.GetString(5, 32);
          if (e.RawData[37] != 0x00)
            i.longTitle = e.GetString(38, 32);
        }
      }
    }

    void receivedArtistTitleChange(PCREventArgs e) {
      if (e.RawData[1] != 0) {
        ChannelInfo i = channels.Channel(e.RawData[1]);
        e.channelInfo = i;

        i.valid = true;
        i.longArtist = e.GetString(3, 16);
        i.shortArtist = i.longArtist;
        i.longTitle = e.GetString(19, 16);
        i.shortTitle = i.longTitle;
        i.updateType = ChannelInfo.UpdateType.shortName;
      }
    }

    String trimNull(String text) {
      return text.TrimEnd('\0').Trim();
    }

    public void Hello() {
      byte[] data = new byte[] {
        0x74, 0x00, 0x01 };
      IO.SendCommand(data);
    }

    int _MuteState = 0;
    public override int MuteState {
      get { return _MuteState; }
      set {
        byte[] cmd = new byte[] { 0x13, (byte)value };
        IO.SendCommand(cmd);
      }
    }

    int _DesiredPowerState = 1;
    public override int DesiredPowerState {
      get {
        return _DesiredPowerState;
      }
      set {
        _DesiredPowerState = value;
      }
    }
  }
}
